/*******************************************************************************
 * Garage Door Automation
 * See http://garageautomation.codeplex.com/
 * Developed for the Rabbit 6000 in the RCM6700 board
 *
 * Door position is determined via a Bourns 3540 10 turn potentiometer
 * feed into CH0 of a MCP3008 10 bit ADC chip
 *
 * The door is activated via the same normally open circuit that the wall
 * switch is hooked to through a KYOTTO KF0602D solid state relay
 ******************************************************************************/

#class auto

#define SPI_MASTER
#define SPI_SER_B
#define SPI_CLK_DIVISOR  8
#define SPI_RX_PORT  SPI_RX_PC
#define NUM_SAMPLES 10
#define ENABLE 0
#define DISABLE 1

#define TCPCONFIG  5       // autoconfig, ie DHCP
#define TCP_BUF_SIZE  2048 // 1024 for send, 1024 for receive
#define HTTP_MAXSERVERS  1
#define MAX_TCP_SOCKET_BUFFERS  1

#define TIMEZONE  -8

#define USE_RABBITWEB  1
#define REDIRECTTO  self_url()

#memmap xmem

#use "subFS.lib"
#use "SPI_4k.lib"
#use "dcrTCP.lib"
#use "HTTP.lib"


void init_io(void);
void adc_chip_select(int state);
void activate_door();
float calculate_voltage(float samples);
float calculate_position(float voltage);
char *self_url();
void delay_ms(unsigned long delay);
short int read_adc(int channel);
float average_samples(int channel);
int index_callback(HttpState* state);
int activate_callback(HttpState* state);
void config_update(void);

#ximport "index.zhtml" index_page
#ximport "config.zhtml" config_page

SSPEC_MIMETABLE_START
   SSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),
   SSPEC_MIME_FUNC( ".shtml", "text/html", shtml_handler),
	SSPEC_MIME(".html", MIMETYPE_HTML),
   SSPEC_MIME( ".cgi", "")
SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_START
	SSPEC_RESOURCE_XMEMFILE("/index.zhtml", index_page),
   SSPEC_RESOURCE_FUNCTION("/index.cgi", index_callback),
   SSPEC_RESOURCE_FUNCTION("/activate.cgi", activate_callback),
   SSPEC_RESOURCE_XMEMFILE("/config.zhtml", config_page)
SSPEC_RESOURCETABLE_END


struct config_struct {
   float y2;
   float y1;
   float x2;
   float x1;
   float m;
   float b;
} config;
#web config;
#web_update config config_update

float position;
#web position;
float sample;
#web sample;
float voltage;
#web voltage;


const float scale_factor = 3.3 / 1024; // Vref / ADC bits

void main ()
{
   int len = sizeof(config);
   get_config(&config, &len);
   /*
   config.y2 = 83;
   config.y1 = 0.0;
   config.x2 = 3.25;
   config.x1 = 0.019;
   config.m = (config.y2 - config.y1) / (config.x2 - config.x1);
   config.b = config.y2 - (config.m * config.x2);
   */
   printf("main: y2=%f y1=%f x2=%f x1=%f m=%f b=%f\n",
           config.y2, config.y1, config.x2, config.x1, config.m, config.b);

   BitWrPortI(PCDR, &PCDRShadow, 0, 0); // init door pin low

   SPIinit();
   init_io();

   adc_chip_select(DISABLE);

   sock_init_or_exit(1);
   http_init();
   http_set_path("/", "index.zhtml");
   tcp_reserveport(80);

   while (1) {
         http_handler();
   }
}

void config_update() {
   config.m = (config.y2 - config.y1) / (config.x2 - config.x1);
   config.b = config.y2 - (config.m * config.x2);
   save_config(&config, sizeof(config));
   printf("config_update: y2=%f y1=%f x2=%f x1=%f m=%f b=%f\n",
          config.y2, config.y1, config.x2, config.x1, config.m, config.b);
}

int index_callback(HttpState* state) {

	sample = average_samples(0);
   voltage = scale_factor * sample;
   position = calculate_position(voltage);

   cgi_redirectto(state, REDIRECTTO);
   return 0;
}

int activate_callback(HttpState* state) {

   activate_door();

	sample = average_samples(0);
   voltage = scale_factor * sample;
   position = calculate_position(voltage);

   cgi_redirectto(state, REDIRECTTO);
   return 0;
}

void activate_door() {
   BitWrPortI(PCDR, &PCDRShadow, 1, 0);
   delay_ms(150);
   BitWrPortI(PCDR, &PCDRShadow, 0, 0);
}

float calculate_position(float voltage) {
   float distance;


   if (voltage <= config.x1) {
       return 0.0;
   }

   distance = (config.m * voltage) + config.b;

   if (distance < 0) {
       return 0.0;
   }

   return distance;
}

char *self_url() {
   static char URL[64];
   char hostname[32];
   long ip;

   ifconfig(IF_DEFAULT, IFG_IPADDR, &ip, IFS_END);
   sprintf(URL, "http://%s/", inet_ntoa(hostname, ip));

   return URL;
}

int save_config(void * data, int data_len)
{
	int rc;

	while ((rc = subfs_create("config", data, data_len)) == -EAGAIN);
	return rc;
}

int get_config(void * data, int * data_len)
{
	int rc;
	unsigned long len = *data_len;	// Initialize to expected buffer length

	while ((rc = subfs_read("config", data, 0, &len)) == -EAGAIN);
	*data_len = (int)len;
	return rc;
}

void delay_ms(unsigned long delay) {
	auto unsigned long done_time;

	for (done_time = MS_TIMER; MS_TIMER - done_time < delay; );
}

float average_samples(int channel) {
  int count;
  int keep;
  short int sample;
  short int samples[NUM_SAMPLES];
  unsigned long accum;
  unsigned long thres;
  unsigned long diff;
  unsigned long avg;
  float results;


  accum = 0L;
  for (count = 0; count < NUM_SAMPLES; count++) {
       sample = read_adc(channel);
       samples[count] = sample;
       accum += sample;
  }
  avg = accum / NUM_SAMPLES;

  thres = (avg / 4) + 10; // ignore readings outside this threshold
  keep = 0;
  accum = 0L;
  for (count = 0; count < NUM_SAMPLES; count++) {
       diff = labs(avg - (unsigned long)samples[count]);
       if (diff < thres) {
           accum += samples[count];
           keep++;
       } else {
           printf("Ignoring samples[%d]=%d diff=%ld thres=%ld\n", count, samples[count], diff, thres);
       }
  }
  if (keep) {
      results = accum / keep;
  } else {
      results = avg;
  }

  return results;
}

short int read_adc(int channel) {
   char cmd[3];
   char res[3];
   char msb;
   char lsb;
   short int sample;


   // See pg 22 of MCP3008 datasheet
   cmd[0] = 0x01; // start bit
   // Control configuration, see pg 19 table 5-2
   // 1000 (1 = single ended input) (000 = ch 0)
   // 1001 single ended, ch 1
   cmd[1] = 0x80 | (channel << 4);
   cmd[2] = 0x00; // don't cares

   adc_chip_select(ENABLE);
	SPIWrRd(cmd, res, 3);
   adc_chip_select(DISABLE);

   msb = res[1] & 0x03; // Only keep two bits
   lsb = res[2];        // The 8 LSB
   sample = (msb << 8) + lsb; // build the resulting int from the 10 bit result

   // printf("\tread_adc(%d) = %d 0x%x  cmd=0x%x\n", channel, sample, sample, cmd[1]);
   return sample;
}

void init_io(void) {
   /* Parallel Port C setup **************************************************/

   // Datasheet pg 111 Parallel Port C Function Register
   // Enable PC4 alt func output  XXX1XXXX
   WrPortI(PCFR, &PCFRShadow,  RdPortI(PCFR) | 0x10);

   BitWrPortI(PCDDR, &PCDDRShadow, 1, 4); // Set PC4 as output (SPI MOSI)
   BitWrPortI(PCDDR, &PCDDRShadow, 0, 5); // Set PC5 as input  (SPI MISO)

   WrPortI(PCAHR, &PCAHRShadow, RdPortI(PCAHR) & 0xF0); // Set PC4 as alt func 0 (TXB)

   // Serial Port B over Parallel Port C pins
   // Serial Port B Control Register (SBCR) datasheet pg 196
   //WrPortI(SBCR, &SBCRShadow, 0xCC); // 1100 1100

   // Serial Port B Extended Register (SBER) datasheet pg 198
   // SPI mode (1,1) - clk idle ?, bits 4,5
   // bit 3 (position 4th right) 0=LSB 1=MSB
   // bits 5:4 = 11 inverted internal clock
   WrPortI(SBER, &SBERShadow, 0x38); // 0X11 1X00
   // SBDLR
   // SBDHR
}

/* Pin PB7 on the RCM6700
 * Set low for enable, high for disable
 */
void adc_chip_select(int state) {
   BitWrPortI(PBDR, &PBDRShadow, state, 7);	// enable /CS
}